﻿using System;
using System.Collections.Generic;

namespace TileGert.Particles
{
  /// <summary>
  /// GertParticleEmitter is a simple particle emitter with a rectangular area.
  /// Particles are spread randomly in all directions.
  /// </summary>
  public class GertParticleEmitter
  {
    private GertGame _game = null;
    private List<GertParticle> _particles = new List<GertParticle>();
    private List<GertParticle> _deadParticles = new List<GertParticle>();
    private Random _random = null;

    private GertPoint _position = new GertPoint();
    private float _birthRate = 1.0f; //Particles created per second
    private float _minSpeed = 10.0f;
    private float _maxSpeed = 10.0f;
    private float _minLife = 20.0f;
    private float _maxLife = 20.0f;
    private float _width = 10.0f;
    private float _height = 10.0f;
    private bool _enabled = false;
    private int _maxParticles = 0; //0 means unlimited
    private string _image = "tilegert_pixel";

    private float _birthCounter = 0.0f; //Used to make sure particles are created with very small timesteps.
    
    /// <summary>
    /// Creates a new particle emitter for the given game.
    /// </summary>
    /// <param name="game"></param>
    public GertParticleEmitter(GertGame game)
    {
      _game = game;
      _random = new Random(Environment.TickCount);
    }

    /// <summary>
    /// Creates a new random position in the emitters rectangular area.
    /// </summary>
    /// <returns>The random position.</returns>
    private GertPoint GetRandomPosition()
    {
      return new GertPoint(_position.X + (float)_random.NextDouble() * _width, _position.Y + (float)_random.NextDouble() * _height);
    }

    /// <summary>
    /// Calculates a random speed in the range MinSpeed ... MaxSpeed.
    /// </summary>
    /// <returns>The random speed.</returns>
    private GertVector GetRandomSpeed()
    {
      //Create a random facing vector
      GertVector result = new GertVector((float)_random.NextDouble() * 2.0f - 1.0f, (float)_random.NextDouble() * 2.0f - 1.0f);
      //Normalize the vector
      result.Normalize();

      //Scale the vector by the random speed
      if (_minSpeed == _maxSpeed)
      {
        result.Scale(_minSpeed);
      }
      else
      {
        //Get a random value in the range _minSpeed ... _maxSpeed
        float randSpeed = _minSpeed + ((float)_random.NextDouble() * (_maxSpeed - _minSpeed));
        result.Scale(randSpeed);
      }

      return result;
    }

    /// <summary>
    /// Calculates a random life time inthe range MinLife ... MaxLife.
    /// </summary>
    /// <returns>The random life value.</returns>
    private float GetRandomLife()
    {
      if (_minLife != _maxLife)
        return _minLife + ((float)_random.NextDouble() * (_minLife - _maxLife));
      else
        return _minLife;
    }

    /// <summary>
    /// Creates a random particle based on the settings of the emitter.
    /// </summary>
    /// <returns>The created random particle.</returns>
    private GertParticle GenerateRandomParticle()
    {
      if (_deadParticles.Count == 0)
        return new GertParticle(GetRandomPosition(), GetRandomSpeed(), _image, GetRandomLife());
      else
      {
        GertParticle result = _deadParticles[0];
        _deadParticles.Remove(result);
        result.Initialize(GetRandomPosition(), GetRandomSpeed(), _image, GetRandomLife());
        return result;
      }
    }

    /// <summary>
    /// Updates the particle positions, removes dead particles from the main list and creates new particles.
    /// </summary>
    /// <param name="timestep">The time to advance the particle simulation.</param>
    public void Update(float timestep)
    {
      if (_enabled)
      {
        int i = 0;
        //Update existing, living particles
        //foreach (GertParticle particle in _particles)
        for (i = 0; i < _particles.Count; i++)
        {
          GertParticle particle = _particles[i];
          if (particle.IsAlife())
            particle.Update(timestep);
          else
          {
            _particles.Remove(particle);
            _deadParticles.Add(particle);
          }
        }

        //Generate new particles
        i = (int)(_birthRate * timestep);
        _birthCounter += _birthRate * timestep;
        if (_birthCounter >= 1.0f)
        {
          i = (int)(_birthCounter) + 1;
          while (i > 0 && _particles.Count < _maxParticles)
          {
            _particles.Add(GenerateRandomParticle());
            i--;
          }
          _birthCounter = 0;
        }
      }
    }

    /// <summary>
    /// Renders all particles to the screen.
    /// </summary>
    public void Render()
    {
      foreach (GertParticle particle in _particles)
      {
        _game.Renderer.DrawSprite(particle);
      }
    }

    /// <summary>
    /// Gets the particle at the given index.
    /// </summary>
    /// <param name="index">The index of the particle.</param>
    /// <returns>The particle at the given index.</returns>
    public GertParticle this[int index]
    {
      get { return _particles[index]; }
      set { _particles[index] = value; }
    }

    /// <summary>
    /// Gets the number of paticles that are alife at the moment.
    /// </summary>
    public int Count
    {
      get { return _particles.Count; }
    }

    /// <summary>
    /// Gets or sets the rate the particles are created. This is measured in particles per second.
    /// </summary>
    public float BirthRate
    {
      get { return _birthRate; }
      set { _birthRate = value; }
    }

    /// <summary>
    /// Gets or sets the minimum speed of the particles. This is measured in pixels per second.
    /// </summary>
    public float MinSpeed
    {
      get { return _minSpeed; }
      set 
      { 
        _minSpeed = value;
        if (_minSpeed > _maxSpeed)
          _maxSpeed = _minSpeed;
      }
    }

    /// <summary>
    /// Gets or sets the maximum speed of the particles. This is measured in pixels per second.
    /// </summary>
    public float MaxSpeed
    {
      get { return _maxSpeed; }
      set 
      { 
        _maxSpeed = value;
        if (_maxSpeed < _minSpeed)
          _minSpeed = _maxSpeed;
      }
    }

    /// <summary>
    /// Gets or sets the minimum time in seconds a particle exists.
    /// </summary>
    public float MinLife
    {
      get { return _minLife; }
      set 
      { 
        _minLife = value;
        if (_minLife > _maxLife)
          _maxLife = _minLife;
      }
    }

    /// <summary>
    /// Gets or sets the maximum time in seconds a particle exists.
    /// </summary>
    public float MaxLife
    {
      get { return _maxLife; }
      set 
      { 
        _maxLife = value;
        if (_maxLife < _minLife)
          _minLife = _maxLife;
      }
    }

    /// <summary>
    /// Gets or sets the with of the rectangular area the particles are spawned from.
    /// </summary>
    public float Width
    {
      get { return _width; }
      set { _width = value; }
    }

    /// <summary>
    /// Gets or sets the height of the rectangular area the particles are spawned from.
    /// </summary>
    public float Height
    {
      get { return _height; }
      set { _height = value; }
    }

    /// <summary>
    /// Determines if the particles are moved and new particles are created.
    /// This does not affect the rendering of the particles.
    /// </summary>
    public bool Enabled
    {
      get { return _enabled; }
      set { _enabled = value; }
    }

    /// <summary>
    /// Gets or sets the image that is drawn for each particle. By default this is a one pixel image.
    /// </summary>
    public string Image
    {
      get { return _image; }
      set { _image = value; }
    }

    /// <summary>
    /// Gets or sets the position of the top left corner of the rectangular area of the emitter.
    /// </summary>
    public GertPoint Position
    {
      get { return _position; }
      set { _position = value; }
    }

    /// <summary>
    /// Gets or sets the maximum number of particles that are simulated simultaniously. A value of 0 means unlimited.
    /// </summary>
    public int MaxParticles
    {
      get { return _maxParticles; }
      set { _maxParticles = value; }
    }

  }
}
